package dbx

import (
	"context"
	"time"

	"github.com/bsm/redislock"
	"github.com/go-redis/redis/v8"
	"zhonghui/console/warehouse/logx"
)

// // 使用方式
//	// 设置锁的时间
//	lockTtl := 5 * time.Second
//	// 设置刷新锁的频率
//	refreshFreq := 200 * time.Millisecond
//	// 创建锁
//	rc := cache_main.Client()
//	lock, err := GetRedisLock(rc, "some_key", lockTtl)
//	if err != nil {
//		fmt.Println("获取锁失败，有其他进程在使用")
//		return
//	}
//	// 创建协程，刷新锁，避免业务处理时间比锁的时间长，lockTtl保持跟加锁的lockTtl一致
//	ch := RefreshLock(lock, refreshFreq, lockTtl)
//
//	defer func() {
//		// 关闭协程 重要！！！！！
//		ch <- 1
//		// 释放锁
//		lock.Release()
//	}()
//
//	fmt.Println("开始处理任务")
//	// 任务开始时，检查锁，严谨
//	if !HasRedisLock(lock) {
//		fmt.Println("已经不再持有锁了，关闭协程，放弃本次操作")
//		// 关闭协程 重要！！！！！
//		ch <- 1
//		return
//	}
//	fmt.Println("处理任务A完毕")
//	// 选择任务处理过程中的适当时间，检查锁，如：进行无法回滚的操作之前
//	if !HasRedisLock(lock) {
//		fmt.Println("已经不再持有锁了，关闭协程，放弃本次操作")
//		return
//	}
//	fmt.Println("处理任务B完毕")
//	fmt.Println("两个任务均处理完毕")
//	// 任务处理挖鼻，检查锁
//	if !HasRedisLock(lock) {
//		fmt.Println("已经不再持有锁了，关闭协程，放弃本次操作")
//		fmt.Println("同时应该回滚上面全部的操作")
//		return
//	}
//	fmt.Println("任务正常处理完毕，关闭协程")

// GetRedisLock 获取redis锁
func GetRedisLock(rc *redis.Client, key string, ttl time.Duration) (*redislock.Lock, error) {
	locker := redislock.New(rc)

	lock, err := locker.Obtain(context.Background(), key, ttl, nil)
	if err != nil && err != redislock.ErrNotObtained {
		logx.Zap().Errorw("分布式加锁失败，redis错误", "err", err)
		return nil, err
	}

	// 被抢占
	if err == redislock.ErrNotObtained {
		return nil, err
	}

	return lock, nil
}

// GetRedisLockWithRefresh 创建一个带刷新器的锁
// freq传0，则不刷新，同时返回的ch是nil
// 使用方式
// lock, ch, _ := dbx.GetRedisLockWithRefresh(cache_main.Client(), "qwe123", time.Minute, 0)
//
//	defer func() {
//		if ch != nil {
//			ch <- 1
//		}
//		lock.Release(context.Background())
//	}()
func GetRedisLockWithRefresh(rc *redis.Client, key string, ttl time.Duration, freq time.Duration) (*redislock.Lock, chan int, error) {
	lock, err := GetRedisLock(rc, key, ttl)
	if err != nil {
		return nil, nil, err
	}

	if freq == 0 {
		return lock, nil, nil
	}

	// 刷新
	ch := make(chan int)
	go func() {
		t := time.NewTicker(freq)
		defer t.Stop()
		for {
			select {
			case <-t.C:
				if err := lock.Refresh(context.Background(), ttl, nil); err != nil {
					logx.Zap().Errorw("分布式锁续期失败",
						"key", lock.Key(),
						"err", err.Error(),
					)
				}
			case <-ch:
				return
			}
		}
	}()

	return lock, ch, nil
}

// HasRedisLock 检查redis锁是否有效
func HasRedisLock(lock *redislock.Lock) bool {
	ttl, err := lock.TTL(context.Background())
	if err != nil {
		logx.Zap().Errorw("分布式锁查询失败",
			"key", lock.Key(),
			"err", err.Error(),
		)
	}

	return ttl > 0
}

// RefreshLock 刷新redis锁，避免业务处理时间比锁的时间长
// 注意关闭chan，否则会有内存溢出的风险，重要！！！！
func RefreshLock(lock *redislock.Lock, freq time.Duration, ttl time.Duration) chan int {
	ch := make(chan int)
	go func() {
		t := time.NewTicker(freq)
		defer t.Stop()
		for {
			select {
			case <-t.C:
				if err := lock.Refresh(context.Background(), ttl, nil); err != nil {
					logx.Zap().Errorw("分布式锁续期失败",
						"key", lock.Key(),
						"err", err.Error(),
					)
				}
			case <-ch:
				return
			}
		}
	}()

	return ch
}
